iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 14
3

https://ithelp.ithome.com.tw/upload/images/20200914/20106426Z3IErPbPDL.jpg

自己第一次聽到 Curry 腦中只浮現以上 XD,認真研究後也是有看沒懂。但比較習慣用 Functional Programming 寫程式後就發現其實 Currying 概念不斷貫穿在其中,希望這篇能用淺顯的敘述來介紹它。

喔對了為什麼會叫 Currying 是因為是因為這個方法的概念的建立者叫做 Haskell Curry


What’s currying?

Transforms a function with multiple arguments to a chain of function applications with one argument each

簡單來說就是 “把接受多個參數的函數變換成接受一個單一參數就叫做 Currying” 。直接來看例子比較清楚,以下是一個單純做變數相加的函式

const add = (a, b) => a + b; 
add(1, 2) // 3

改成 Currying 後就會變

const add = a => b => a + b; 
add(1)(2) // 3  
// ---- below is common way -------
const add1 = add(1) 
add1(2) // 3

你會發現 Currying 一次只接受 “一個” 參數

https://cdn-images-1.medium.com/max/2000/1*2oOejHHCvsRXGxwrnjmxQg.jpeg

f = (a, b) => value
f(a, b)

// become currying
f = a => b => value
g = f(a); // return function b => value
g(b); // return value;

// 也可以
f(a)(b)


export default function curry(func) {
  return function inner(...args) {
    if (args.length === func.length) {
      return func.apply(this, args);
    } else {
      return (arg) => {
        return arg === undefined
          ? inner.apply(this, [...args])
          : inner.apply(this, [...args, arg]);
      };
    }
  };
}

它的原理也很簡單,利用 closure 特性,將 f(a) 存放在 g 中 ,待最後參數 b 傳入,完成運算 g(b)。

Why use currying?

  • DRY (Don’t Repeat Yourself ): 有別於以往一個函式處理所有事情,Currying 可以將程式碼依功能拆解成更小單元,有助於重複利用 (這也是 FP 精華之一)
  • 函式參數越多,處理程式碼時會更加繁複。currying 一次處理一個參數,提高程式的彈性和可讀性

以下是一個判斷你分數有沒有有 Pass 的簡單函式

const uncurriedGradeTest=(passGrade, failGrade, average, testScore) =>
testScore >= average ? passGrade : failGrade;

// Repeat the same args many times
uncurriedGradeTest( 'Pass', 'Fail', 0.2, 0.19 )
uncurriedGradeTest( 'Pass', 'Fail', 0.2, 0.39 )
uncurriedGradeTest( 'Pass', 'Fail', 0.2, 0.5 )
uncurriedGradeTest( 'Pass', 'Fail', 0.2, 0.1 )
uncurriedGradeTest( 'Pass', 'Fail', 0.2, 0.8 )

你會發現一直需要丟重覆的參數 Pass 、Fail 跟平均分數 0.2,那換成 Currying 呢?

// Currying
const getGradeTest = (passGrade, failGrade) => average => testScore => 
testScore >= average ? passGrade : failGrade;

const passFaillTest = getGradeTest( 'Pass', 'Fail' )( 0.2 );

passFaillTest( 0.19 )
passFaillTest( 0.39 )
passFaillTest( 0.5 )
passFaillTest( 0.1 )
passFaillTest( 0.8 )

是不是乾淨多了呢!

Partial application vs. currying

常看到有人把這兩個當成同樣東西,事實是他們的確很像,也都是利用 closure 特性但卻是不一樣的

https://cdn-images-1.medium.com/max/2000/1*pQmrhtx9Swlbciq2O7BTvA.jpeg

// Currying
const add = a => b => c => a + b + c; 
add(1)(2)(3) // 6
// Partial application
const add = a => (b, c) => a + b + c; 
add(1)(2, 3) // 6
  • currying: Creates a chain of unary functions
  • Partial application: operates with functions of any arity

-|currying | Partial application
------------- | -------------
arity | 1 | variable (f, ...args)
bind arguments? | No | Yes

所以最大不同就是 currying 只接收一個 Arity,而 Partial application 可以接受多個

Arity?

the number of arguments of a function

f( a ) // arity: 1 
f( a, b ) // arity: 2 
f(...args) // arity: 0

Partial application example

// partial Application
const partial = 
(f, ...args1) => 
(...args2) => 
f(...args1 , ...args2);

const volume = (a, b c) => a*b*c;

console.log( partial( volume, 2, 3 )( 4 ) ) // 24
console.log( partial( volume, 2 )( 3, 4 ) ) // 24

f.bind(null, ...args)

下一篇會舉大量例子讓大家更了解 Curry


參考文章

如有錯誤或需要改進的地方,拜託跟我說。
我會以最快速度修改,感謝您

歡迎追蹤我的部落格,除了技術文也會分享一些在矽谷工作的甘苦。


上一篇
[補充] HOF 的好友 HOC (Higher-Order Component)
下一篇
[練習] Currying Exercise
系列文
Functional Programming in JS30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
ytyubox
iT邦新手 5 級 ‧ 2020-09-17 21:51:01

封面圖有趣~

我要留言

立即登入留言